package com.TexasHoldem.xSocket.engine;

import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import net.sf.json.JSONObject;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.IConnection.FlushMode;
import org.xsocket.connection.IConnectionTimeoutHandler;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.IDisconnectHandler;
import org.xsocket.connection.IIdleTimeoutHandler;
import org.xsocket.connection.INonBlockingConnection;

import com.TexasHoldem.ServerData.PokerPlayer;
import com.TexasHoldem.xSocket.data.Msg;

import flex.messaging.io.amf.ASObject;

public class SocketHandler implements IDataHandler, IDisconnectHandler,
		IIdleTimeoutHandler, IConnectionTimeoutHandler {

	private static Log log = LogFactory.getLog(SocketHandler.class);
	
	private long lastReceiveDataTime;

	private boolean isFrist;

	private int state;

	private int dataLen = 0;

	private final AtomicInteger recordCount = new AtomicInteger(0);

	private int remaining = 0;

	private ByteBuffer buff = null;

	private static int MAX_RECV_BUFFER = 4 * 1024 * 1024;
	
	private static int MAX_SEND_BUFFER = 1024 * 1024;

	private AbstractEngine engine = null;

	private boolean activeFlag;

	private INonBlockingConnection nbc;

	private String remoteIp;

	private long id;

	private boolean isHashPassword;

	private String version;

	private final Lock writeBcBfLock = new ReentrantLock();

	private Object accountName;
	
	public final static String POLICY_STR = "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\""
			+ "21-60000" + "\" /></cross-domain-policy>\0";

	public final static int POlICY_LEN = 23;

	private static int SEND_BUFFER_GUARD_LINE = (int) (1024 * 1024 * 0.8);
	
	private PokerPlayer pokerplayer = null;

	public PokerPlayer getPokerplayer() {
		return pokerplayer;
	}


	public void setPokerplayer(PokerPlayer pokerplayer) {
		this.pokerplayer = pokerplayer;
	}


	public SocketHandler(long id, AbstractEngine server,
			INonBlockingConnection nbc) throws IOException {
		this.activeFlag = true;
		this.engine = server;
		this.nbc = nbc;
		this.id = id;
		remoteIp = nbc.getRemoteAddress().getHostAddress();
		nbc.setHandler(this);
		nbc.setAutoflush(true);
		nbc.setFlushmode(FlushMode.ASYNC);
		nbc.setIdleTimeoutMillis(10 * 60 * 1000);
		// nbc.setOption(INonBlockingConnection.SO_SNDBUF, arg1)
	}


	@Override
	public boolean onConnectionTimeout(INonBlockingConnection arg0)
			throws IOException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean onIdleTimeout(INonBlockingConnection arg0)
			throws IOException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean onDisconnect(INonBlockingConnection arg0) throws IOException {
		// TODO Auto-generated method stub
		System.out.print("onDisconnect:" + this.getPokerplayer().getName());
		return false;
	}

	@Override
	public boolean onData(INonBlockingConnection nbc) throws IOException,
			BufferUnderflowException, ClosedChannelException,
			MaxReadSizeExceededException {
		// TODO Auto-generated method stub
		
		if (nbc == null || !nbc.isOpen()) {
			return true;
		}

		this.lastReceiveDataTime = System.currentTimeMillis();

		int len = nbc.available();
		
		if (log.isDebugEnabled()) {
			log.debug("onData:" + len);
		}

		// ���SWF�����Ƿ�Ȩ�ް�
		if (this.isFrist && len == POlICY_LEN) 
		{
			this.isFrist = false;
			this.state = ConnectionState.INITIAL;
			this.checkPolicy(nbc);
			return true;
		}
		if(len == -1)
			return true;

		if (dataLen < 1) {

			recordCount.addAndGet(1);

			dataLen = nbc.readInt();
			
			if (log.isDebugEnabled())
				log.debug("New record<" + recordCount.get() + "> come,dataLen=" + dataLen);

			if (dataLen < 0 || dataLen > MAX_RECV_BUFFER) 
			{
				this.onDisconnect(nbc); // �����ܴ���4M�İ�
				return false;
			}

			remaining = dataLen;

			buff  = ByteBuffer.allocate(dataLen);
		}

		int available = nbc.available();

		if (available == -1)
			return true;

		if (available > 0) {

			int readLen = remaining;
			if (available < readLen) {
				readLen = available;
			}

			ByteBuffer[] buffers = null;
			buffers = nbc.readByteBufferByLength(readLen);
			remaining -= readLen;
			
			for (int i = 0; i < buffers.length; i++) {
				ByteBuffer bf = buffers[i];
				if (bf != null) 
					buff.put(bf);
			}

		}

		if (dataLen > 0 && remaining < 1) 
		{
			if (log.isDebugEnabled())
				log.debug("Read all data,dataLen=" + dataLen);

			String str = new String(buff.array());

			JSONObject j = JSONObject.fromObject(str);
				
			engine.handleRequest(j.getString("cmd"), j, this);
			
			buff.clear();
			buff = null;
			dataLen = 0;
		}
		return true;
	}
	private void checkPolicy(INonBlockingConnection nbc) throws IOException {
		nbc.readByteBufferByLength(23);
		nbc.write(POLICY_STR);

		if (log.isDebugEnabled()) {
			log.debug("send policy:" + POLICY_STR);
		}
		
	}


	public void SetHashPassword(boolean isHashPassword) {
		this.isHashPassword = isHashPassword;
	}


	public void setVersion(String version) {
		// TODO Auto-generated method stub
		this.version = version;
	}


	public void sendResponse(String cmd, Object data) {
		addResponse(cmd, data);
	}
	public void addResponse(String cmd, Object data) {
		// buffer.add(buff);
		long time = System.currentTimeMillis();
		try 
		{
			if (nbc == null || !nbc.isOpen())
				return;
			if (nbc.getPendingWriteDataSize() >= MAX_SEND_BUFFER) 
			{
				byte[] ba = EngineUtil.toByteArray(cmd, data);
				log.error(this.remoteIp + " cmd:" + cmd + " dataLen:" + ba.length + " " + this.getAccountName() + "  nbc.getPendingWriteDataSize() >= " + MAX_SEND_BUFFER);

				this.onDisconnect(nbc);

				return;
			}
			JSONObject s = new JSONObject();
			s.put("cmd", cmd);
			s.put("data", data);
			byte[] jsondata = s.toString().getBytes();
			ByteBuffer buff = ByteBuffer.allocate(jsondata.length + 4);

			buff.putInt(jsondata.length);
			
			
			buff.put(jsondata);
			buff.flip();

			
			nbc.write(buff);
			

			engine.addSendLen(jsondata.length + 4);

			
		} catch (BufferOverflowException e) {
			log.trace(e);
		} catch (ClosedChannelException e) {
			log.trace(e);
		} catch (IOException e) {
			log.trace(e);
		} catch (Throwable e) {
			log.trace(e);
		}
	}

	private String getAccountName() {
		// TODO Auto-generated method stub
		return null;
	}


	/**
	 * �Ͽ�����
	 * 
	 * @param immediately
	 *            true:���϶Ͽ� false:���һ��kick out���������
	 */
	public void disconnect(boolean immediately) {
		this.accountName = null;

		synchronized (this) {
			if (immediately) {
				try {
					if (nbc != null && nbc.isOpen()) {
						nbc.close();
						nbc = null;
						activeFlag = false;
						if (log.isDebugEnabled())
							log.debug("Close nbc connect");
					}

				} catch (IOException e) {
					log.error(e);
				}
			} else {
				Msg d = new Msg("Kick out");
				addResponse(AbstractEngine.CMD_KICKOUT, d);
			}
		}
	}
}
